﻿using System;
using System.Linq;
using NHibernate.Cfg;
using System.Reflection;
using NHibernate;
using InnovTech.Data.DataAccess;

namespace InnovTech.Data.NHibernate
{
    public static class NHibernateExtensions
    {

        #region Configuration
        public static void AddMappingFile(this Configuration config, MappingFile file)
        {
            Assembly assembly = Assembly.Load(file.AssemblyName);
            config.AddResource(file.FilePath, assembly);
        }

        public static void AddMappingFileAssembly(this Configuration config, MappingFileAssembly mfAssembly)
        {
            Assembly assembly = mfAssembly.GetAssembly();
            if (mfAssembly.MappingFiles.Count == 0)
                config.AddAssembly(assembly);
            else
            {
                var mappingFiles = mfAssembly.GetMappingFiles();
                if(mappingFiles != null && mappingFiles.Any())
                    config.AddResources(mappingFiles, assembly);
            }
        }

        #endregion Configuration

        #region ISession

        public static void DoUnitOfWork(this ISession session, Action<ISession> unitOfWork, bool closeSession = true)
        {
            DoUnitOfWork<object>(session, currentSession => { unitOfWork(currentSession); return null; }, closeSession);
        }

        public static T DoUnitOfWork<T>(this ISession session, Func<ISession, T> unitOfWork, bool closeSession = true)
        {
            session.FlushMode = FlushMode.Never;
            T result = default(T);
            using (var tx = session.BeginTransaction())
            {
                try
                {
                    result = unitOfWork(session);
                    tx.Commit();
                }
                finally
                {
                    if (tx != null)
                    {
                        tx.Rollback();
                        session.Close();
                    }
                }
            }
            if (closeSession)
                session.Close();

            return result;
        }

        /// <summary>
        /// Updates the given detached object. After the update the given object will be reattached onto the current session.
        /// If the given object is mapped with versioning, and the auto-increasement of the version for a NO-Changing update is prohibited,
        /// Then the following attributes should be correctly set on the CLASS element in the mapping file for the given object,
        ///     &lt;class dynamic-update="true" select-before-update="true"
        /// The details sees http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/mapping.html
        /// </summary>
        /// <param name="session"></param>
        /// <param name="entity"></param>
        public static void UpdateDetachedObject(this ISession session, IEntity entity)
        {
            var persisted = session.Get(entity.GetType(), entity.ID);

            // the given object is persisted
            if (persisted != null && !session.Contains(entity))
                // Evicts the persisted object from the current session to guarantee that there is no any object which has the same identity with the given object.
                session.Evict(persisted);

            // The given object is REATTACHED onto the current session after this execution, and all changing from the given object are accepted by the current session.
            // The given object is NOT DETACHED anymore until the current session is closed.
            session.SaveOrUpdate(entity);
            // Flushes all changing of this session and DB accepts them.
            session.Flush();

            // Only for updating all DateTime properties whose millisecond section is truncated by NHibernate automatically while saving. 
            // To synchronize the DateTime properties of the current object with the DB, the methed ISession.Refresh should be called directly after ISession.Flush
            session.Refresh(entity);
        }

        /// <summary>
        /// Refreshes the given object with the newest state from the DB.
        /// </summary>
        /// <param name="session"></param>
        /// <param name="entity"></param>
        public static void RefreshDetachedObject(this ISession session, IEntity entity)
        {
            //un-saved, after Calling ISession.Refresh() nothing will be changed.
            if (entity.ID == default(long))
            {
                return;
            }

            //Refresh the given object directly, if it is associated on the current session.
            if (session.Contains(entity))
                session.Refresh(entity);
            else
            {
                //If the given object is not associated on the current session.
                var persisted = session.Get(entity.GetType(), entity.ID);
                if (persisted != null)
                {
                    //guarantees the current session contains no object with the same id of the given object
                    session.Evict(persisted);
                    session.Refresh(entity);
                }
                else
                {
                    //In the DB there is no persisted object with the same if of the given object.
                    //Can not call the method ISession.Refresh(). It causes Exception. 
                }
            }
        }

        #endregion ISession
    }
}
